package pl.edu.mimuw.irs.core.model.petrinet;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import pl.edu.mimuw.irs.core.exception.InternalException;
import pl.edu.mimuw.irs.core.exception.InternalException.Cause;
import pl.edu.mimuw.irs.core.global.IDManager;


/**
 * @author Jakub Rauch
 * Created on: 2009-05-07
 */
public class Net implements Serializable {
	
	private static final long serialVersionUID = -6499440391536034031L;

	// *** Attributes
	
	/** Collection of all places in the net. It maps the ids to the concrete places instances. */
	private Map<String, IPlace> placesMap;
	/** Collection of all transitions in the net. It maps the ids to the concrete transitions instances. */
	private Map<String, ITransition> transitionsMap;
	/** Collection of all arcs in the net. It maps the ids to the concrete arcs instances. */
	private Map<String, Arc> arcsMap;
	/** ID generator for this net. Every child component added to the net will get this manager as its id generator. */
	private IDManager idManager;
	// Getters and setters
	private Map<String, IPlace> getPlacesMap() {
		if (placesMap == null) placesMap = new LinkedHashMap<String, IPlace>();
		return Collections.unmodifiableMap(placesMap);
	}
	private Map<String, ITransition> getTransitionsMap() {
		if (transitionsMap == null) transitionsMap = new LinkedHashMap<String, ITransition>();
		return Collections.unmodifiableMap(transitionsMap);
	}
	private Map<String, Arc> getArcsMap() {
		if (arcsMap == null) arcsMap = new LinkedHashMap<String, Arc>();
		return Collections.unmodifiableMap(arcsMap);
	}
	public Collection<IPlace> getPlaces() {
		return Collections.unmodifiableCollection(placesMap.values());
	}
	public Collection<ITransition> getTransitions() {
		return Collections.unmodifiableCollection(transitionsMap.values());
	}
	public Collection<Arc> getArcs() {
		return Collections.unmodifiableCollection(arcsMap.values());
	}
	/** Returns place in the net identified by given id. */
	public IPlace getPlace(String id) {
		return getPlacesMap().get(id);
	}
	/** Returns transition in the net identified by given id. */
	public ITransition getTransition(String id) {
		return getTransitionsMap().get(id);
	}
	/** Returns arc in the net identified by given id. */
	public Arc getArc(String id) {
		return getArcsMap().get(id);
	}
	public IDManager getIdManager() {
		if (idManager == null) idManager = new IDManager();
		return idManager;
	}
	public void setIdManager(IDManager idManager) {
		this.idManager = idManager;
	}
	
	/** Gets all existing nodes in the net. */
	public Set<INode> getAllNodes() {
		Set<INode> allNodes = new LinkedHashSet<INode>();
		allNodes.addAll(getPlaces());
		allNodes.addAll(getTransitions());
		return allNodes;
	}
	
	// *** Constructors
	
	public Net(Set<IPlace> places, Set<ITransition> transitions, Set<Arc> arcs) {
		addPlaces(places);
		addTransitions(transitions);
		addArcs(arcs);
	}
	
	public Net() {
		this(new HashSet<IPlace>(), new HashSet<ITransition>(), new HashSet<Arc>());
	}
	
	// *** Methods
	
	/** Adds place using its currently generated id. Place is not modified. It is treated as properly built
	 * with properly attached arcs. */
	public void addPlace(IPlace place) {
		addPlaces(Collections.singleton(place));
	}
	/** Removes place by given id. The place instance is not modified in any way. */
	public void removePlace(String placeId) {
		removePlaces(Collections.singleton(placeId));
	}
	/** Adds places using their currently generated id. Places are not modified. They are treated as properly built
	 * with properly attached arcs. */
	public void addPlaces(Collection<IPlace> places) {
		getPlacesMap();
		Map<String, IPlace> placesMap = this.placesMap;
		for (IPlace place : places) {
			placesMap.put(place.getId(), place);
		}
	}
	/** Removes places by given ids. The places instances are not modified in any way. */
	public void removePlaces(Set<String> placesIds) {
		getPlacesMap();
		Map<String, IPlace> placesMap = this.placesMap;
		for (String placeId : placesIds) {
			placesMap.remove(placeId);
		}
	}

	/** Adds transition using its currently generated id. Transition is not modified. It is treated as properly built
	 * with properly attached arcs. */
	public void addTransition(ITransition transition) {
		addTransitions(Collections.singleton(transition));
	}
	/** Removes transition by given id. The transition instance is not modified in any way. */
	public void removeTransition(String transitionId) {
		removeTransitions(Collections.singleton(transitionId));
	}
	/** Adds transitions using their currently generated id. Transitions are not modified. They are treated as properly built
	 * with properly attached arcs. */
	public void addTransitions(Collection<ITransition> transitions) {
		getTransitionsMap();
		Map<String, ITransition> transitionsMap = this.transitionsMap;
		for (ITransition transition : transitions) {
			transitionsMap.put(transition.getId(), transition);
		}
	}
	/** Removes transitions by given ids. The transitions instances are not modified in any way. */
	public void removeTransitions(Set<String> transitionsIds) {
		getTransitionsMap();
		Map<String, ITransition> transitionsMap = this.transitionsMap;
		for (String transitionId : transitionsIds) {
			transitionsMap.remove(transitionId);
		}
	}
	
	/** Adds arc given in the parameter. Arc is not modified but will be validated for proper binding to already
	 * existing nodes in this net.
	 * @param arc
	 */
	public void addArc(Arc arc) {
		addArcs(Collections.singleton(arc));
	}
	/** Adds arc given in the parameter. Arcs are not modified but will be validated for proper binding to already
	 * existing nodes in this net.
	 * @param arc
	 */
	public void addArcs(Collection<Arc> arcs) {
		getArcsMap();
		Map<String, Arc> arcsMap = this.arcsMap;
		for (Arc arc : arcs) {
			if (!getPlacesMap().containsValue(arc.getPlace()) ||
					!getTransitionsMap().containsValue(arc.getTransition()))
				throw new InternalException(Cause.PETRI_NET_NOT_INNER_ARC, arc, arc.getPlace(), arc.getTransition());
			arcsMap.put(arc.getId(), arc);
		}
	}
	/** Removes arc given by id from the parameter. Arc is not modified in any way. */
	public void removeArc(String arcId) {
		removeArcs(Collections.singleton(arcId));
	}
	/** Removes arcs given by ids from the parameter. Arc are not modified in any way. */
	public void removeArcs(Set<String> arcsIds) {
		getArcsMap();
		Map<String, Arc> arcsMap = this.arcsMap;
		for (String arcId : arcsIds) {
			arcsMap.remove(arcId);
		}
	}
//	
//	/** Creates new arc according to all given parameters.
//	 * @param placeId identifier of the place-end of the edge. It must exist in the net. Otherwise the exception will be thrown.
//	 * @param transitionId identifier of the transition-end of the edge. It must exist in the net. Otherwise the exception will be thrown.
//	 * @param direction direction of the arc, must be not null.
//	 * @param weight weight of the arc, must be non-negative.
//	 */
//	private void connect(String sourceId, String targetId, int weight) {
//		ITransition transition = get getTransitionsMap().get(transitionId);
//		IPlace place = getPlacesMap().get(placeId);
//		if (transition == null) throw new InternalException(Cause.PETRI_NET_UNDEFINED_ELEMENT, transitionId);
//		if (place == null) throw new InternalException(Cause.PETRI_NET_UNDEFINED_ELEMENT, placeId);
//		Arc arc = new Arc(idManager, place, transition, direction, weight);
//		place.addArc(arc);
//		transition.addArc(arc);
//		addArc(arc);
//	}
//	/** Creates new arc according to all given parameters. Direction is defaulted to PT.
//	 * @param placeId identifier of the place-end of the edge. It must exist in the net. Otherwise the exception will be thrown.
//	 * @param transitionId identifier of the transition-end of the edge. It must exist in the net. Otherwise the exception will be thrown.
//	 * @param weight weight of the arc, must be non-negative.
//	 */
//	public void connectPT(String placeId, String transitionId, int weight) {
//		connect(placeId, transitionId, Direction.PLACE_TRANSITION, weight);
//	}
//	/** Creates new arc according to all given parameters. Direction is defaulted to TP.
//	 * @param placeId identifier of the place-end of the edge. It must exist in the net. Otherwise the exception will be thrown.
//	 * @param transitionId identifier of the transition-end of the edge. It must exist in the net. Otherwise the exception will be thrown.
//	 * @param weight weight of the arc, must be non-negative.
//	 */
//	public void connectTP(String transitionId, String placeId, int weight) {
//		connect(placeId, transitionId, Direction.TRANSITION_PLACE, weight);
//	}
//	/** Creates new arc according to all given parameters. Direction is defaulted to PT. Weight is defaulted to 1.
//	 * @param placeId identifier of the place-end of the edge. It must exist in the net. Otherwise the exception will be thrown.
//	 * @param transitionId identifier of the transition-end of the edge. It must exist in the net. Otherwise the exception will be thrown.
//	 */
//	public void connectPT(String placeId, String transitionId) {
//		connectPT(placeId, transitionId, 1);
//	}
//	/** Creates new arc according to all given parameters. Direction is defaulted to TP. Weight is defaulted to 1.
//	 * @param placeId identifier of the place-end of the edge. It must exist in the net. Otherwise the exception will be thrown.
//	 * @param transitionId identifier of the transition-end of the edge. It must exist in the net. Otherwise the exception will be thrown.
//	 */
//	public void connectTP(String transitionId, String placeId) {
//		connectTP(transitionId, placeId, 1);
//	}
	/** Creates new arc according to all given parameters. Direction is defaulted to PT.
	 * @param place place-end of the edge. It must exist in the net. Otherwise the exception will be thrown.
	 * @param transition transition-end of the edge. It must exist in the net. Otherwise the exception will be thrown.
	 */
	public void connect(Place place, Transition transition, int weight) {
		if (!getPlacesMap().containsValue(place) || 
				getTransitionsMap().containsValue(transition))
			throw new InternalException(Cause.PETRI_NET_NOT_INNER_ARC, place, transition);
		Arc arc = new Arc(getIdManager(), place, transition, weight);
		addArc(arc);
	}
	/** Creates new arc according to all given parameters. Direction is defaulted to PT. Weight is defaulted to 1.
	 * @param place place-end of the edge. It must exist in the net. Otherwise the exception will be thrown.
	 * @param transition transition-end of the edge. It must exist in the net. Otherwise the exception will be thrown.
	 */
	public void connect(Place place, Transition transition) {
		connect(place, transition, 1);
	}
	/** Creates new arc according to all given parameters. Direction is defaulted to PT.
	 * @param place place-end of the edge. It must exist in the net. Otherwise the exception will be thrown.
	 * @param transition transition-end of the edge. It must exist in the net. Otherwise the exception will be thrown.
	 */
	public void connect(Transition transition, Place place, int weight) {
		if (!getPlacesMap().containsValue(place) || 
				getTransitionsMap().containsValue(transition))
			throw new InternalException(Cause.PETRI_NET_NOT_INNER_ARC, place, transition);
		Arc arc = new Arc(getIdManager(), transition, place, weight);
		addArc(arc);
	}
	/** Creates new arc according to all given parameters. Direction is defaulted to PT. Weight is defaulted to 1.
	 * @param place place-end of the edge. It must exist in the net. Otherwise the exception will be thrown.
	 * @param transition transition-end of the edge. It must exist in the net. Otherwise the exception will be thrown.
	 */
	public void connect(Transition transition, Place place) {
		connect(transition, place, 1);
	}
	
	/** General method for adding nodes to the net. If the node is neither Place nor Transition an exception will be thrown. */
	public void addNode(INode node) {
		if (node instanceof Place) {
			addPlace((Place) node);
		} else if (node instanceof Transition) {
			addTransition((Transition) node);
		} else {
			throw new InternalException(Cause.PETRI_NET_UNKNOWN_NODE_TYPE);
		}
	}
	/** General method for removing nodes from the net. If the node is neither Place nor Transition an exception will be thrown. */
	public void removeNode(INode node) {
		if (node instanceof Place) {
			removePlace(((Place) node).getId());
		} else if (node instanceof Transition) {
			removeTransition(((Transition) node).getId());
		} else {
			throw new InternalException(Cause.PETRI_NET_UNKNOWN_NODE_TYPE);
		}
	}
	
	/** General method for adding elements to the net. If the node is neither Node nor Arc,
	 * an exception will be thrown. */
	public void addElement(IElement element) {
		if (element instanceof INode) {
			addNode((INode) element);
		} else if (element instanceof Arc) {
			addArc((Arc) element);
		} else {
			throw new InternalException(Cause.PETRI_NET_UNKNOWN_ELEMENT_TYPE);
		}
	}
	/** General method for removing elements from the net. If the node is neither Node nor Arc,
	 * an exception will be thrown. */
	public void removeElement(IElement element) {
		if (element instanceof INode) {
			removeNode((INode) element);
		} else if (element instanceof Arc)  {
			removeArc(element.getId());
		} else {
			throw new InternalException(Cause.PETRI_NET_UNKNOWN_ELEMENT_TYPE);
		}
	}
		
	/**
	 * Searches whole graph to find all active transitions and returns them.
	 * @return
	 */
	public List<ITransition> getActiveTransitions() {
		List<ITransition> activeTransitions = new ArrayList<ITransition>();
		
		for (ITransition transition : getTransitions()) {
			if (transition.isActive()) {
				activeTransitions.add(transition);
			}
		}
		
		return activeTransitions;
	}
	
	/**
	 * Loads given net contents to this net. The only operations performed are:<br/>
	 *  - adding all places from given net to this instance<br/>
	 *  - adding all transitions from given net to this instance<br/>
	 *  - adding all arcs from given net to this instance<br/>
	 * @param net
	 */
	protected void loadNet(Net net) {
		loadNodes(net);
		loadArcs(net);
	}

	/**
	 * Loads nodes from given net contents to this net. The only operations performed are:<br/>
	 *  - adding all places from given net to this instance<br/>
	 *  - adding all transitions from given net to this instance<br/>
	 * @param net
	 */
	protected void loadNodes(Net net) {
		addPlaces(net.getPlaces());
		addTransitions(net.getTransitions());
	}

	/**
	 * Loads arcs from given net contents to this net. The only operations performed are:<br/>
	 *  - adding all arcs from given net to this instance<br/>
	 * @param net
	 */
	protected void loadArcs(Net net) {
		addArcs(net.getArcs());
	}
}
